/*
 * 文件名:CAlsa.cpp
 *
 * 	 功能描述:
 *  创建日期: 2010-11-27
 *  版本Ver0.01
 *  作者: huanying2000
 *  版权:copyright 2010 by the Camellia project 
 *  free software under the terms of the LGPL 2.1
 */

#include "CAlsa.hpp"

CAlsa::CAlsa()
{
	formatMap[0].alsaFormat = SND_PCM_FORMAT_S16;
	formatMap[0].mpg123Format = MPG123_ENC_SIGNED_16;

	formatMap[1].alsaFormat = SND_PCM_FORMAT_U16;
	formatMap[1].mpg123Format = MPG123_ENC_UNSIGNED_16;

	formatMap[2].alsaFormat = SND_PCM_FORMAT_U8;
	formatMap[2].mpg123Format = MPG123_ENC_UNSIGNED_8;

	formatMap[3].alsaFormat = SND_PCM_FORMAT_S8;
	formatMap[3].mpg123Format = MPG123_ENC_SIGNED_8;

	formatMap[4].alsaFormat = SND_PCM_FORMAT_A_LAW;
	formatMap[4].mpg123Format = MPG123_ENC_ALAW_8;

	formatMap[5].alsaFormat = SND_PCM_FORMAT_MU_LAW;
	formatMap[5].mpg123Format = MPG123_ENC_ULAW_8;

	formatMap[6].alsaFormat = SND_PCM_FORMAT_S32;
	formatMap[6].mpg123Format = MPG123_ENC_SIGNED_32;

	formatMap[7].alsaFormat = SND_PCM_FORMAT_U32;
	formatMap[7].mpg123Format = MPG123_ENC_UNSIGNED_32;

	formatMap[8].alsaFormat = SND_PCM_FORMAT_FLOAT;
	formatMap[8].mpg123Format = MPG123_ENC_FLOAT_32;

	formatMap[9].alsaFormat = SND_PCM_FORMAT_FLOAT64;
	formatMap[9].mpg123Format = MPG123_ENC_FLOAT_64;
}

CAlsa::~CAlsa()
{
	 snd_mixer_free(mixerFd);
	 close(mixer);
}

//比率检测
int CAlsa::ratesMatch(long desiredRates, unsigned int actualRates)
{
	return (actualRates * 100 > desiredRates * (100 - AUDIO_RATE_TOLERANCE)
				&& actualRates * 100 < desiredRates * (100 + AUDIO_RATE_TOLERANCE)
				);
}

int CAlsa::initializeDevice(KAudioOutput *ao)
{
	snd_pcm_hw_params_t *hw=NULL;
	snd_pcm_sw_params_t *sw=NULL;
	snd_pcm_uframes_t buffer_size;
	snd_pcm_uframes_t period_size;
	snd_pcm_format_t format;
	snd_pcm_t *pcm=(snd_pcm_t*)ao->userptr;
	unsigned int rate;

	snd_pcm_hw_params_alloca(&hw); /* Ignore GCC warning here... alsa-lib>=1.0.16 doesn't trigger that anymore, too. */
	if (snd_pcm_hw_params_any(pcm, hw) < 0)
	{
		cerr << "initializeDevice(): no configuration available!\n";
		return -1;
	}

	if (snd_pcm_hw_params_set_access(pcm, hw, SND_PCM_ACCESS_RW_INTERLEAVED) < 0)
	{
		cerr << "initializeDevice(): device does not support interleaved access!\n";
		return -1;
	}

	format = SND_PCM_FORMAT_UNKNOWN;
	for (int i = 0; i < formatNumber; ++i)
	{
		if (ao->format == formatMap[i].mpg123Format)
		{
			format = formatMap[i].alsaFormat;
			break;
		}
	}

	if (format == SND_PCM_FORMAT_UNKNOWN)
	{
		cerr << "initializeDevice(): invalid sample format " << ao->format << "!\n";
		errno = EINVAL;
		return -1;
	}

	if (snd_pcm_hw_params_set_format(pcm, hw, format) < 0)
	{
		cerr << "initializeDevice(): cannot set format " << snd_pcm_format_name(format) << "!\n";
		return -1;
	}

	if (snd_pcm_hw_params_set_channels(pcm, hw, ao->channels) < 0)
	{
		cerr << "initializeDevice(): cannot set " <<  ao->channels << "channels!\n";
		return -1;
	}

	rate = ao->rate;
	if (snd_pcm_hw_params_set_rate_near(pcm, hw, &rate, NULL) < 0)
	{
		//if(!AOQUIET) error1("initialize_device(): cannot set rate %u", rate);
		cerr << "initializeDevice(): cannot set rate " << rate << "!\n";
		return -1;
	}

	if (!ratesMatch(ao->rate, rate))
	{
		//if(!AOQUIET) error2("initialize_device(): rate %ld not available, using %u", ao->rate, rate);
		cerr << "nitializeDevice(): rate " << ao->rate << " not available, using " << rate << "!\n";
		/* return -1; */
	}

	buffer_size = rate * BUFFER_LENGTH;
	if (snd_pcm_hw_params_set_buffer_size_near(pcm, hw, &buffer_size) < 0)
	{
		//if(!AOQUIET) error("initialize_device(): cannot set buffer size");
		cerr << "initializDevice(): cannot set buffer size!\n";
		return -1;
	}

	period_size = buffer_size / 4;
	if (snd_pcm_hw_params_set_period_size_near(pcm, hw, &period_size, NULL) < 0)
	{
		//if(!AOQUIET) error("initialize_device(): cannot set period size");
		cerr << "initializeDevice(): cannot set period size!\n";
		return -1;
	}

	if (snd_pcm_hw_params(pcm, hw) < 0)
	{
		//if(!AOQUIET) error("initialize_device(): cannot set hw params");
		cerr << "initializeDevice(): cannot set hw params!\n";
		return -1;
	}

	snd_pcm_sw_params_alloca(&sw);
	if (snd_pcm_sw_params_current(pcm, sw) < 0)
	{
		//if(!AOQUIET) error("initialize_device(): cannot get sw params");
		cerr << "initializeDevice(): cannot get sw params!\n";
		return -1;
	}

	/* start playing after the first write */
	if (snd_pcm_sw_params_set_start_threshold(pcm, sw, 1) < 0)
	{
		//if(!AOQUIET) error("initialize_device(): cannot set start threshold");
		cerr << "initializeDevice(): cannot set start threshold!\n";
		return -1;
	}

	/* wake up on every interrupt */
	if (snd_pcm_sw_params_set_avail_min(pcm, sw, 1) < 0)
	{
		//if(!AOQUIET) error("initialize_device(): cannot set min available");
		cerr << "initializeDevice(): cannot set min available!\n";
		return -1;
	}

#if SND_LIB_VERSION < ((1<<16)|16)
	/* Always write as many frames as possible (deprecated since alsa-lib 1.0.16) */
	if (snd_pcm_sw_params_set_xfer_align(pcm, sw, 1) < 0)
	{
		//if(!AOQUIET) error("initialize_device(): cannot set transfer alignment");
		cerr << "initializeDevice(): cannot set transfer alignment!\n";
		return -1;
	}
#endif

	if (snd_pcm_sw_params(pcm, sw) < 0)
	{
		//if(!AOQUIET) error("initialize_device(): cannot set sw params");
		cerr << "initializeDevice(): cannot set sw params!\n";
		return -1;
	}

	return 0;
}

int CAlsa::openOutput(KAudioOutput *ao)
{
	const char *pcm_name;
	snd_pcm_t *pcm=NULL;

	cerr << "openAlsa() with " <<  ao->userptr << "!\n";

	snd_lib_error_set_handler(NULL);

	pcm_name = ao->device ? ao->device : "default";
	cout << "pcm_name:" << pcm_name << "!\n";
	if (snd_pcm_open(&pcm, pcm_name, SND_PCM_STREAM_PLAYBACK, 0) < 0)
	{
		//if(!AOQUIET) error1("cannot open device %s", pcm_name);
		cerr << "cannot open device " << pcm_name << "!\n";
		return -1;
	}

	ao->userptr = pcm;
	if (ao->format != -1)
	{
		initializeMixter();
		return initializeDevice(ao);	/* we're going to play: initalize sample format */
	}
	else
	{
		return 0;								/* query mode; sample format will be set for each query */
	}
}


int CAlsa::getFormatsAlsa(KAudioOutput *ao)
{
	snd_pcm_t *pcm=(snd_pcm_t*)ao->userptr;
	snd_pcm_hw_params_t *hw;
	unsigned int rate;
	int supported_formats;

	snd_pcm_hw_params_alloca(&hw);
	if (snd_pcm_hw_params_any(pcm, hw) < 0)
	{
		//if(!AOQUIET) error("get_formats_alsa(): no configuration available");
		cerr << "getFormatsAlsa():no configuration available!\n";
		return -1;
	}

	if (snd_pcm_hw_params_set_access(pcm, hw, SND_PCM_ACCESS_RW_INTERLEAVED) < 0)
		return -1;

	if (snd_pcm_hw_params_set_channels(pcm, hw, ao->channels) < 0)
		return 0;

	rate = ao->rate;
	if (snd_pcm_hw_params_set_rate_near(pcm, hw, &rate, NULL) < 0)
		return -1;

	if (!ratesMatch(ao->rate, rate))
		return 0;

	supported_formats = 0;
	for (int i = 0; i < formatNumber; ++i)
	{
		if (snd_pcm_hw_params_test_format(pcm, hw, formatMap[i].alsaFormat) == 0)
			supported_formats |= formatMap[i].mpg123Format;
	}

	return supported_formats;
}

int CAlsa::writeOutput(KAudioOutput *ao, unsigned char *buf, int bytes)
{
	snd_pcm_t *pcm=(snd_pcm_t*)ao->userptr;
	snd_pcm_uframes_t frames;
	snd_pcm_sframes_t written;

	frames = snd_pcm_bytes_to_frames(pcm, bytes);
	written = snd_pcm_writei(pcm, buf, frames);

	if (written == -EINTR) /* interrupted system call */
		written = 0;
	else if (written == -EPIPE)
	{ /* underrun */
		if (snd_pcm_prepare(pcm) >= 0)
			written = snd_pcm_writei(pcm, buf, frames);
	}

	if (written >= 0)
		return snd_pcm_frames_to_bytes(pcm, written);
	else
	{
		if(snd_pcm_state(pcm) == SND_PCM_STATE_SUSPENDED)
		{
			/* I am not happy abou this not happy really not. */
			snd_pcm_resume(pcm);
			if(snd_pcm_state(pcm) == SND_PCM_STATE_SUSPENDED)
			{
				//error("device still suspended after resume hackery... giving up");
				cerr << "device still suspended after resume hackery... giving up!\n";
				return -1;
			}
		}
		return 0;
	}
}

void CAlsa::flushOutput(KAudioOutput *ao)
{
	snd_pcm_t *pcm=(snd_pcm_t*)ao->userptr;

	/* is this the optimal solution? - we should figure out what we really whant from this function */
//debug("alsa drop");
	cerr << "alsa drop!\n";
	snd_pcm_drop(pcm);

//debug("alsa prepare");
	cerr << "alsa prepare!\n";
	snd_pcm_prepare(pcm);

//debug("alsa flush done");
	cerr << "lasa flush done!\n";
}

int CAlsa::closeOutput(KAudioOutput *ao)
{
	snd_pcm_t *pcm=(snd_pcm_t*)ao->userptr;

	//debug1("close_alsa with %p", ao->userptr);
	cerr << "closeAlsa() with  " << ao->userptr << "!\n";

	if(pcm != NULL) 								/* be really generous for being called without any device opening */
	{
		if (snd_pcm_state(pcm) == SND_PCM_STATE_RUNNING)
			snd_pcm_drain(pcm);

		ao->userptr = NULL; 					/* Should alsa do this or the module wrapper? */
		return snd_pcm_close(pcm);
	}
	else
		return 0;
}

int CAlsa::initializeMixter()
{
	int result;
	// 打开混音器
	 if ((result = snd_mixer_open( &mixerFd, 0)) < 0)
	 {
	        cerr << "snd_mixer_open error" << result << '\n';
	        mixerFd = NULL;
	        return result;
	 }
	// Attach an HCTL to an opened mixer
	 if ((result = snd_mixer_attach( mixerFd, "default")) < 0)
	 {
	        cerr << "snd_mixer_attach error" << result << '\n';
	        snd_mixer_close(mixerFd);
	        mixerFd = NULL;
	        return result;
	 }
	// 注册混音器
	 if ((result = snd_mixer_selem_register( mixerFd, NULL, NULL)) < 0)
	{
	        cerr <<"snd_mixer_selem_register error"<<result;
	        snd_mixer_close(mixerFd);
	        mixerFd = NULL;
	        return result;
	}
	// 加载混音器
	if ((result = snd_mixer_load( mixerFd)) < 0)
	{
	        cerr <<"snd_mixer_load error"<<result;
	        snd_mixer_close(mixerFd);
	        mixerFd = NULL;
	        return result;
	}

	if ((mixer = open("/dev/mixer", O_WRONLY)) == -1)
	{
		cerr << "open /dev/mixer error!\n";
		return -1;
	}


	return 0;
}

int CAlsa::setVolume(long val)
{
	long minVolume;
	long maxVolume;

	/*for(mixerElem =snd_mixer_first_elem(mixerFd); mixerElem; mixerElem=snd_mixer_elem_next(mixerElem))
	{
		if (snd_mixer_elem_get_type(mixerElem) == SND_MIXER_ELEM_SIMPLE && snd_mixer_selem_is_active(mixerElem)) // 找到可以用的, 激活的elem
		{
			if(strcmp(snd_mixer_selem_get_name(mixerElem), "Master") == 0)
			{
				//snd_mixer_selem_set_playback_volume_range(mixerElem, 0, 100);
				//snd_mixer_selem_get_playback_volume_range(mixerElem, &minVolume, &maxVolume);
				//snd_mixer_selem_set_playback_volume_all(mixerElem, val); // 设置音量为50
				//snd_mixer_selem_set_playback_volume(mixerElem, val); // 设置音量为50
				//snd_mixer_selem_set_playback_volume(mixerElem, SND_MIXER_SCHN_FRONT_LEFT, val);
				//snd_mixer_selem_set_playback_volume(mixerElem, SND_MIXER_SCHN_FRONT_RIGHT, val);
				cout << mixerElem << "-> minVolume:" << minVolume << ", maxVolume:" << maxVolume << "\n";
				//break;
			}
		}
	}*/

	//ioctl(mixer, SOUND_MIXER_WRITE(SOUND_MIXER_IMIX), &val);
	if (-1 == ioctl(mixer, SOUND_MIXER_WRITE_IMIX, &val))
	{
		cerr << "SOUND_MIXER_IMIX error!\n";
	}
	else
	{
		cout << "ioctl is ok...\n";
	}

	return 0;
}
